package net.sunofbeach.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import net.sunofbeach.blog.mapper.ImageMapper;
import net.sunofbeach.blog.pojo.Image;
import net.sunofbeach.blog.pojo.User;
import net.sunofbeach.blog.pojo.query.ImageQuery;
import net.sunofbeach.blog.response.Result;
import net.sunofbeach.blog.service.ImageService;
import net.sunofbeach.blog.service.UserService;
import net.sunofbeach.blog.utils.Constants;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Bing
 * @since 2021-11-24
 */
@Slf4j
@Service
public class ImageServiceImpl extends ServiceImpl<ImageMapper, Image> implements ImageService {

    @Value("${sob.blog.image.save-path}")
    public String imagePath;

    @Value("${sob.blog.image.max-size}")
    public Long maxSize;

    @Resource
    private UserService userService;


    /**
     * 单文件上传至本地
     * 上传路径：可以配置，在配置文件里配置
     * 限制上传文件大小，通过配置文件配置
     * 保存记录到数据库：ID|存储路径|url|原名称|用户ID|状态|创建日期|更新日期
     *
     * @param original
     * @param file
     * @return
     */
    @Override
    public Result uploadImage(String original, MultipartFile file) {

        // 判断是否有文件
        if (file == null) {
            return Result.fail().message("图片不可以为空");
        }
        // 判断文件类型，我们只支持图片上传，比如说：png、jpg、gif
        String contentType = file.getContentType();
        log.info("contentType ==> " + contentType);

        if (StringUtils.isEmpty(contentType)) {
            return Result.fail().message("图片格式错误");
        }
        // 图片类型常量：png、jpg、gif (经测试只支持上传png格式的图片 ）
        // 根据我们定的规则进行命名
        String originalFilename = file.getOriginalFilename();
        log.info("originalFilename ==> " + originalFilename);
        // 获取文件类型（方法抽取）
        String type = getType(contentType, originalFilename);
        if (type == null) {
            return Result.fail().message("不支持此图片类型");
        }

        // 限制上传的文件大小
        long size = file.getSize();
        log.info("maxSize ==> " + maxSize + " size ==> " + size);
        if (size > maxSize) {
            return Result.fail().message("图片最大仅支持 " + (maxSize / 1024 / 1024) + "MB");
        }
        // 创建图片的保存目录
        // 规则：配置目录/日期/类型/ID.类型/
        long currentTimeMillis = System.currentTimeMillis();

        String currentDay = new SimpleDateFormat("yyyy_MM_dd").format(currentTimeMillis);
        log.info("current day ==> " + currentDay);

        String dayPath = imagePath + File.separator + currentDay;
        File dayPathFile = new File(dayPath);
        // 判断日期文件夹是否存在，不存在就创建
        if (!dayPathFile.exists()) {
            dayPathFile.mkdirs();
        }
        // 采用 UUID 命名，把 UUID 里的 - 转换为 空
        String targetName = UUID.randomUUID().toString().replace("-", "");

        // 拼接文件路径和文件名
        String targetPath = dayPath + File.separator + type + File.separator + targetName + "." + type;
        File targetFile = new File(targetPath);
        // 判断类型文件夹是否存在
        if (!targetFile.getParentFile().exists()) {
            targetFile.mkdirs();
        }
        try {
            // 文件不存在，就创建
            if (!targetFile.exists()) {
                targetFile.createNewFile();
            }
            log.info("targetFile ==> " + targetFile);
            // 上传成功后路径和文件名
            // D:\Code\Java\SobBlogSystem\Image\2021-12-02\png\4273ddd0b0264b6091b2d643e2a3be5b.png
            // 保存文件
            file.transferTo(targetFile);
            // 返回结果：包含这个图片的名称和访问路径
            // 第一个是访问路径 一一》 要对应着解析 : 时间戳 + _ + UUID + . + 图片格式类型
            String resultPath = currentTimeMillis + "_" + targetName + "." + type;
            Map<String, String> result = new HashMap<>();
            result.put("id", resultPath);
            // 第二个是名称 一一》 alt="图片描述"，如果不写，前端可以使用名称作为这个描述
            result.put("name", originalFilename);
            // 记录文件
            // TODO 优化：保存之前先检查是否有相同的图片，没有才上传
            // 保存记录到数据库
            Image image = new Image();
            image.setContentType(contentType);
            image.setTargetId(targetName);
            image.setPath(targetFile.getPath());
            image.setName(originalFilename);
            image.setUrl(resultPath);
            image.setOriginal(original);
            User user = userService.checkSobUser();
            image.setUserId(user.getId());
            // 日期自动填充，这里不用设置
            baseMapper.insert(image);

            // 返回结果
            return Result.ok(result).message("上传文件成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Result.fail().message("图片上传失败，请稍后重试");
    }

    /**
     * 方法抽取-获取文件类型
     * 获取文件的后缀名 统一转为小写再进行判断
     *
     * @param contentType
     * @param name
     * @return
     */
    private String getType(String contentType, String name) {
        String type = null;
        if (Constants.ImageType.PNG_WITH_PREFIX.equals(contentType) &&
                name.toLowerCase().endsWith(Constants.ImageType.PNG)) {
            type = Constants.ImageType.PNG;
        } else if (Constants.ImageType.JPG_WITH_PREFIX.equals(contentType) &&
                name.toLowerCase().endsWith(Constants.ImageType.JPG)) {
            type = Constants.ImageType.JPG;
        } else if (Constants.ImageType.GIF_WITH_PREFIX.equals(contentType) &&
                name.toLowerCase().endsWith(Constants.ImageType.GIF)) {
            type = Constants.ImageType.GIF;
            // 这里处理上传不了 jpg 格式图片
        } else if (Constants.ImageType.JPEG_WITH_PREFIX.equals(contentType) &&
                name.toLowerCase().endsWith(Constants.ImageType.JPG)) {
            type = Constants.ImageType.JPEG;
        } else if (Constants.ImageType.JPEG_WITH_PREFIX.equals(contentType) &&
                name.toLowerCase().endsWith(Constants.ImageType.JPEG)) {
            type = Constants.ImageType.JPEG;
        }
        return type;
    }



    /**
     * 获取图片
     *
     * @param response
     * @param imageId
     * @return
     */
    @Override
    public void viewImage(HttpServletResponse response, String imageId) throws IOException {
        // 优化：根据尺寸来动态返回图片给前端
        // 好处：减少带宽占用，传输速度快
        // 缺点：消耗后台的 cpu 资源
        // 推荐做法：上传上来的时候，把图片复制成三个尺寸：大、中、小
        // 根据尺寸范围，返回结果即可。

        // 配置的目录已知：  D:\Code\Java\SobBlogSystem\Image
        // 需要日期：  \2021-12-02\
        // 需要类型：  png\
        // ID：  4273ddd0b0264b6091b2d643e2a3be5b
        // 使用日期的时候戳_ID.类型：   .png
        // 完整路径： D:\Code\Java\SobBlogSystem\Image\2021-12-02\png\2f6919fc033d46c5973e7480479905cd.png
        // 上传成功后 "path": "1638423841805_2f6919fc033d46c5973e7480479905cd.png",

        // 解析：
        // 分割字符串
        String[] paths = imageId.split("_");
        // 前面的是时间戳
        String dayValue = paths[0];

        String format;
        // 加锁（解决图片读取不出来）
        // 格式化时间戳
        format = new SimpleDateFormat("yyyy_MM_dd").format(Long.parseLong(dayValue));
        log.info("format ==> " + format);

        // 后面的是 uuid.类型
        String name = paths[1];
        // 截取图片类型
        //String type = name.substring(name.length() - 3);
        // 截图点之后所有字符串
        String type = name.substring(name.lastIndexOf(".") + 1);

        // 拼接路径
        String targetPath = imagePath + File.separator + format + File.separator + type + File.separator + name;
        log.info("image targetPath ==> " + targetPath);

        File file = new File(targetPath);
        OutputStream writer = null;
        FileInputStream fos = null;
        try {
            response.setContentType("image/png");
            writer = response.getOutputStream();
            // 读取
            fos = new FileInputStream(file);
            byte[] buff = new byte[1024];
            int len;
            while ((len = fos.read(buff)) != -1) {
                writer.write(buff, 0, len);
            }
            writer.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                fos.close();
            }
            if (writer != null) {
                writer.close();
            }
        }
        // postMan 中通过 path 获取图片：GET  http://localhost:2021/admin/image/1638423841805_2f6919fc033d46c5973e7480479905cd.png
    }


    /**
     * 条件分页查询图片列表
     *
     * @param pageParam
     * @param query
     * @return
     */
    @Override
    public IPage<Image> selectPage(Page<Image> pageParam, ImageQuery query) {
        LambdaQueryWrapper<Image> wrapper = new LambdaQueryWrapper<>();
        if (!StringUtils.isEmpty(query.getUserId())) {
            wrapper.like(Image::getUserId, query.getUserId());
        }
        if (!StringUtils.isEmpty(query.getTargetId())) {
            wrapper.eq(Image::getTargetId, query.getTargetId());
        }
        if (!StringUtils.isEmpty(query.getName())) {
            wrapper.like(Image::getName, query.getName());
        }
        if (!StringUtils.isEmpty(query.getState())) {
            wrapper.eq(Image::getState, query.getState());
        }
        if (!StringUtils.isEmpty(query.getContentType())) {
            wrapper.eq(Image::getContentType, query.getContentType());
        }
        if (!StringUtils.isEmpty(query.getBegin())) {
            wrapper.ge(Image::getCreateTime, query.getBegin());
        }
        if (!StringUtils.isEmpty(query.getEnd())) {
            wrapper.le(Image::getCreateTime, query.getEnd());
        }
        if (!StringUtils.isEmpty(query.getOriginal())) {
            wrapper.eq(Image::getOriginal, query.getOriginal());
        }
        wrapper.orderByDesc(Image::getCreateTime);
        return baseMapper.selectPage(pageParam, wrapper);
    }


}
